<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Python | Joshua Hull</title>
    <link>https://joshua_hull.gitlab.io/tags/python/</link>
      <atom:link href="https://joshua_hull.gitlab.io/tags/python/index.xml" rel="self" type="application/rss+xml" />
    <description>Python</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>© 2021 Joshua Hull - Licensed under Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.</copyright><lastBuildDate>Fri, 05 May 2017 01:09:22 -0400</lastBuildDate>
    <image>
      <url>https://joshua_hull.gitlab.io/images/icon_hu0b7a4cb9992c9ac0e91bd28ffd38dd00_9727_512x512_fill_lanczos_center_2.png</url>
      <title>Python</title>
      <link>https://joshua_hull.gitlab.io/tags/python/</link>
    </image>
    
    <item>
      <title>Lambda Function and Encrypted S3</title>
      <link>https://joshua_hull.gitlab.io/post/2017-05-05-lambda-function-and-encrypted-s3/</link>
      <pubDate>Fri, 05 May 2017 01:09:22 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2017-05-05-lambda-function-and-encrypted-s3/</guid>
      <description>&lt;p&gt;One of the aspects of AWS Lambda&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; that makes it excepent is that Lambda is used to extend other services offered by AWS. In this example we will set up Lambda to use Server Side Encryption for any object uploaded to AWS S3&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The first task we have is to write the lambda function. Below we have the Python code that will read in the metadata about the object that was uploaded and copy it to the same path in the same S3 bucket if SSE is not enabled.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import boto3
import os
import sys
import uuid

def check_if_unencrypted(bucket, key):
    s3 = boto3.resource(&#39;s3&#39;)
    obj = s3.Object(&#39;bucket_name&#39;,&#39;key&#39;)

    return not obj.server_side_encryption


def handler(event, context):
    s3 = boto3.client(&#39;s3&#39;)

    for record in event[&#39;Records&#39;]:
        bucket = record[&#39;s3&#39;][&#39;bucket&#39;][&#39;name&#39;]
        key = record[&#39;s3&#39;][&#39;object&#39;][&#39;key&#39;]

        if check_if_unencrypted(bucket, key):
            download_path = &#39;/tmp/{}&#39;.format(uuid.uuid4())

            s3.download_file(bucket, key, download_path)
            data = open(&#39;test.jpg&#39;, &#39;rb&#39;)

            s3.put_object(Bucket=bucket, ServerSideEncryption=&#39;AES256&#39;, Body=data)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have out lambda function written we need to create the lambda function inside AWS. The following commands will create the AWS role for Lambda. We first need to create two files. The first is the Trust Policy for the IAM role that will allow Lambda to assume the role.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
  &amp;quot;Statement&amp;quot;: [
    {
      &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
      &amp;quot;Principal&amp;quot;: {
        &amp;quot;Service&amp;quot;: &amp;quot;lambda.amazonaws.com&amp;quot;
      },
      &amp;quot;Action&amp;quot;: &amp;quot;sts:AssumeRole&amp;quot;
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The second file will be the permissions that go along with the role. Note that these permissions give full access to the bucket. Use with caution.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
  &amp;quot;Statement&amp;quot;: {
    &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
    &amp;quot;Action&amp;quot;: &amp;quot;s3:*&amp;quot;,
    &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:s3:::example_bucket&amp;quot;
  },
  {
        &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
        &amp;quot;Action&amp;quot;: [
          &amp;quot;logs:*&amp;quot;
        ],
        &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:logs:*:*:*&amp;quot;
      }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have those two files, refered from here on as trust.json and permissions.json, we can run the commands to create the role and the lambda function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
aws iam create-role --role-name LambdaRole --assume-role-policy-document file://trust.json

aws iam put-role-policy --role-name LambdaRole --policy-name S3FullAccess --policy-document file://permissions.json

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we&#39;ve created the role for Lambda to use we can create the function. We&#39;ll need to ZIP up the code and then upload it for Lambda to run. We will also need to the role ARN from above when we create the function.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
zip -9 main.zip main.py

aws lambda create-function \
--region us-east-1 \
--function-name EncryptS3 \
--zip-file fileb://main.zip \
--role arn:aws:iam:us-east-1:123456789012:role:LambdaRole \
--handler main.handler \
--runtime python3.6 \
--profile adminuser \
--timeout 10 \
--memory-size 1024

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to configure both Lambda and S3 to handle notifying Lambda when an object is places in an S3 bucket. We will need another JSON file, policy.json, with the following content that will allow the Lambda Function to access objects in the S3 bucket.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
{
   &amp;quot;Statement&amp;quot;: [
      {
         &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
         &amp;quot;Principal&amp;quot;: {
            &amp;quot;AWS&amp;quot;: &amp;quot;arn:aws:lambda:us-east-1:123456789012:function:LambdaRole&amp;quot;
         },
         &amp;quot;Action&amp;quot;: [
            &amp;quot;s3:*&amp;quot;
         ],
         &amp;quot;Resource&amp;quot;: [
            &amp;quot;arn:aws:s3:::example_bucket/*&amp;quot;,
            &amp;quot;arn:aws:s3:::example_bucket&amp;quot;
         ]
      }
   ]
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;
aws lambda add-permission \
--function-name EncryptS3 \
--region us-east-1 \
--statement-id some-unique-id \
--action lambda:InvokeFunction \
--principal s3.amazonaws.com \
--source-arn arn:aws:s3:::example_bucket \
--source-account 123456789012 \
--profile adminuser

aws s3api put-bucket-policy --bucket MyBucket --policy file://policy.json

aws iam create-role \
  --role-name InvokeLambdaRole \
  --assume-role-policy-document &#39;{
      &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
      &amp;quot;Statement&amp;quot;: [
        {
          &amp;quot;Sid&amp;quot;: &amp;quot;&amp;quot;,
          &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
          &amp;quot;Principal&amp;quot;: {
            &amp;quot;Service&amp;quot;: &amp;quot;s3.amazonaws.com&amp;quot;
          },
          &amp;quot;Action&amp;quot;: &amp;quot;sts:AssumeRole&amp;quot;,
          &amp;quot;Condition&amp;quot;: {
            &amp;quot;StringLike&amp;quot;: {
              &amp;quot;sts:ExternalId&amp;quot;: &amp;quot;arn:aws:s3:::*&amp;quot;
            }
          }
        }
      ]
    }&#39;

aws iam put-role-policy \
  --role-name InvokeLambdaRole \
  --policy-name InvokeLambdaPolicy \
  --policy-document &#39;{
     &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,
     &amp;quot;Statement&amp;quot;: [
       {
         &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,
         &amp;quot;Action&amp;quot;: [
           &amp;quot;lambda:InvokeFunction&amp;quot;
         ],
         &amp;quot;Resource&amp;quot;: [
           &amp;quot;*&amp;quot;
         ]
       }
     ]
   }&#39;

aws s3api put-bucket-notification \
  --bucket example_bucket \
  --notification-configuration &#39;{
    &amp;quot;CloudFunctionConfiguration&amp;quot;: {
      &amp;quot;CloudFunction&amp;quot;: &amp;quot;arn:aws:lambda:us-east-1:123456789012:function:LambdaRole&amp;quot;,
      &amp;quot;InvocationRole&amp;quot;: &amp;quot;arn:aws:iam:us-east-1:123456789012:role:InvokeLambdaRole&amp;quot;,
      &amp;quot;Event&amp;quot;: &amp;quot;s3:ObjectCreated:*&amp;quot;
    }
  }&#39;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s everything that&#39;s needed. You should be able to upload an object to the S3 bucket and it will be re-uploaded with Server Side Encryption. Go ahead and give it a try and let me know what you think in the comments below. If you have an questions or issues leave a comment or reach out to me on twitter.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/lambda&#34;&gt;https://aws.amazon.com/lambda&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/s3&#34;&gt;https://aws.amazon.com/s3&lt;/a&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Developing Visualization for Security Groups</title>
      <link>https://joshua_hull.gitlab.io/post/2017-03-29-ec2-connected-graph/</link>
      <pubDate>Wed, 29 Mar 2017 13:02:32 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2017-03-29-ec2-connected-graph/</guid>
      <description>&lt;p&gt;I was working on a task yesterday and throught I would write it up so that others could possibly benefit from it. I was working to document our AWS enviornment, specifically the security groups around each instance and how the instances are connected to each other and the internet as a whole.&lt;/p&gt;
&lt;p&gt;I had been asked several weeks ago if there was some documentation of the AWS environment at work and how instances were interconnected. I didn&#39;t have any documentation at the time and it wasn&#39;t a huge deal so I let the topic drop. The other day however I was thinking about documenting AWS and that conversion came back into my head. I realized that with the AWS API you could generate the graph of the connections realtivly easily. Since everyone loves pretty pictures I wanted to see if I could visualize it as well.&lt;/p&gt;
&lt;p&gt;The first step was to generate the graph of the security groups. This was another chance to use boto3&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;, the de-facto Python binding for the AWS API. After toying around I had the code that could generate the graph between each instance and the IPs that were allowed inbound access.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import boto3
import json

def searchIpAddress(ipAddress):
    inputAddress = ipAddress.split(&#39;/&#39;)[0]
    returnString = ipAddress

    ec2 = boto3.resource(&#39;ec2&#39;)
    for instance in ec2.instances.all():
        for interface in instance.network_interfaces_attribute:
             if interface[&#39;PrivateIpAddress&#39;] == inputAddress:
                 returnString = instance.instance_id
             if &#39;Association&#39; in interface:
                if interface[&#39;Association&#39;][&#39;PublicIp&#39;] == inputAddress:
                    returnString = instance.instance_id
    return returnString

ec2 = boto3.resource(&#39;ec2&#39;)

graph = {}
cache = {}

for instance in ec2.instances.all():
    graph[instance.instance_id] = {}
    for interface in instance.network_interfaces_attribute:
        for group in interface[&#39;Groups&#39;]:
            security_group = ec2.SecurityGroup(group[&#39;GroupId&#39;])
            for permission in security_group.ip_permissions:
                for IP in permission[&#39;IpRanges&#39;]:
                    if IP[&#39;CidrIp&#39;] not in cache.keys():
                        cache[IP[&#39;CidrIp&#39;]] = searchIpAddress(IP[&#39;CidrIp&#39;])
                    source = cache[IP[&#39;CidrIp&#39;]]
                    if IP[&#39;CidrIp&#39;] in graph[instance.instance_id].keys():
                        graph[instance.instance_id][source] = graph[instance.instance_id][source] + 1
                    else:
                        graph[instance.instance_id][source] = 1

nodes = []
links = []

for node in graph:
    nodes.append({&#39;id&#39;: node, &#39;group&#39;: 1})
    for edge in graph[node]:
        group = 2
        if edge.split(&#39;-&#39;)[0] == &#39;i&#39;:
            group = 1
        nodes.append({&#39;id&#39;: edge, &#39;group&#39;: group})
        links.append({&amp;quot;source&amp;quot;: edge, &amp;quot;target&amp;quot;: node, &amp;quot;value&amp;quot;: graph[node][edge]})

seen_nodes = set()
nodes_dedup = []
for obj in nodes:
    if obj[&#39;id&#39;] not in seen_nodes:
        nodes_dedup.append(obj)
        seen_nodes.add(obj[&#39;id&#39;])

data = {&#39;nodes&#39;: nodes_dedup, &#39;links&#39;: links}

print(json.dumps(data))

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s one aspect of the code that I want to specifically call out. The method &lt;code&gt;searchIpAddress&lt;/code&gt; searches for the instance ID for an IP address. This way if a security group refrences another instance the graph can properly know that. If we were to do this blindly though we would be pulling the list of instances from the API for each IP address for each port. Since we don&#39;t want to be wasteful we cache the results and preform a lookup in the cache and only call the method if we&#39;ve not encountered that IP address before.&lt;/p&gt;
&lt;p&gt;Now that we have the graph we need a way to visualize it. Here I wanted to use a Directed Graph&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt; from D3&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;, the JavaScript visualization library. The python code above will output the graph in a way that can be used by D3. While I was able to get the graph working correctly I felt like the visualization was lacking. The graph couldn&#39;t handle the fact that the secutiy groups would have multiple ports vert well. It also couldn&#39;t handle the structure of our network very well but that was certainly no fault of the graph.&lt;/p&gt;
&lt;p&gt;Overall I&#39;m happy with the exercise. While the visualization aspect didn&#39;t turn out how I hoped I was able to get the data and have a way to reproduce it in the future should anyone need it. I hope to incorporate RDS as well in the future but that presents a different set of challenges.&lt;/p&gt;
&lt;p&gt;If you&#39;ve got questions about Amazon Web Services&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; or cloud technology in general feel free to contact me and I&#39;ll see how I can help bring my experience to the problem.&lt;/p&gt;
&lt;section class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://boto3.readthedocs.io/en/latest/&#34;&gt;https://boto3.readthedocs.io/en/latest/&lt;/a&gt; &lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:2&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://bl.ocks.org/mbostock/4062045&#34;&gt;https://bl.ocks.org/mbostock/4062045&lt;/a&gt; &lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:3&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://d3js.org/&#34;&gt;https://d3js.org/&lt;/a&gt; &lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&#34;fn:4&#34; role=&#34;doc-endnote&#34;&gt;
&lt;p&gt;&lt;a href=&#34;https://aws.amazon.com/&#34;&gt;https://aws.amazon.com/&lt;/a&gt; &lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</description>
    </item>
    
    <item>
      <title>Architecting Serverless Dynamic DNS Using AWS Services</title>
      <link>https://joshua_hull.gitlab.io/post/2016-06-24-serverless-dynamic-dns/</link>
      <pubDate>Fri, 24 Jun 2016 16:52:00 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2016-06-24-serverless-dynamic-dns/</guid>
      <description>&lt;p&gt;The inspiration for this post and much of its content comes from &lt;a href=&#34;https://medium.com/aws-activate-startup-blog/building-a-serverless-dynamic-dns-system-with-aws-a32256f0a1d8#.6tzj1o286&#34;&gt;https://medium.com/aws-activate-startup-blog/building-a-serverless-dynamic-dns-system-with-aws-a32256f0a1d8#.6tzj1o286&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You&#39;ve recently set up a server at your home. You don&#39;t quite feel comfortable hosting it in a service like AWS or you happened to have a machine lying around you want to try and get some use out of. You&#39;ve gotten it up and running and forwarded incoming traffic from your router to be forwarded to the server. You set up the DNS and are happy with the results.&lt;/p&gt;
&lt;p&gt;Several weeks go by and you&#39;re at work. The weather is bad and you find out power was interrupted at your home. You are worried about the server (You didn&#39;t use a surge protector or a UPS, did you?) and decide to try and connect. As you fear you can&#39;t you go about your work and head home at the end of the day. The weather is clear and you arrive home to find the power is on. You try to connect to your server and everything is fine. You work into the evening and go to bed.&lt;/p&gt;
&lt;p&gt;The next day at work you are trying to connect to your server again and find you can&#39;t. You try everything but nothing works. You get home and find you can connect fine. You decide to check the external IP address has changed. You call your ISP and find out that they issue dynamic addresses to residential customers and either won&#39;t give you a static one or are going to charge you far too much for one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://joshua_hull.gitlab.io/img/_posts/aws-dynamic-dns.png&#34; alt=&#34;Dynamic DNS in AWS&#34;&gt;&lt;/p&gt;
&lt;p&gt;After some research you develop a plan to use &lt;a href=&#34;%5Bhttps://aws.amazon.com/api-gateway/%5D&#34;&gt;AWS API Gateway&lt;/a&gt; and &lt;a href=&#34;https://aws.amazon.com/lambda&#34;&gt;AWS Lamda&lt;/a&gt;. You plan on having a single API endpoint with two modes, &lt;code&gt;get&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt;. The &lt;code&gt;get&lt;/code&gt; method will simply return the IP address of whoever called the API. An example of calling the endpoint in the &lt;code&gt;get&lt;/code&gt; mode is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
wget https://....amazonaws.com/prod?mode=get

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The return value for the call will be the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “176.32.100.36”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The client can then use this information to calculate a secure SHA256 hash of the information it needs to pass to the API in the &lt;code&gt;set&lt;/code&gt; mode. This hash will consist of &lt;code&gt;IP_AddressHost_NameShared_Secret&lt;/code&gt;. If the client wants to update the IP address for &lt;code&gt;host1.dyn.example.com&lt;/code&gt; to &lt;code&gt;192.168.0.1&lt;/code&gt; with the shared secret of &lt;code&gt;P@ssw0rd&lt;/code&gt; then it would pass &lt;code&gt;SHA256(192.168.0.1host1.dyn.example.comP@ssw0rd)&lt;/code&gt; in the &lt;code&gt;set&lt;/code&gt; as show below:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
HASH=`echo -n 192.168.0.1host1.dyn.example.comP@ssw0rd | shasum -a 256`

wget https://....amazonaws.com/prod?mode=set&amp;amp;hostname=host1.dyn.example.com&amp;amp;hash=$HASH

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hostname does not need to be updated the following will be the return value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “Your IP address matches the current Route53 DNS record.”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the hostname is updated then the following will be the return value:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
  “return_message”: “Your hostname record host1.dyn.example.com. has been set to 176.32.100.36”,
  “return_status”: “success”
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You setup the API inside of AWS and configure it to use Lamda as the backend. You create a single Lamda function to use and after some trial and error have the following result:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
from __future__ import print_function

import json
import re
import hashlib
import boto3

config_s3_region = &#39;us-west-2&#39;
config_s3_bucket = &#39;my_bucket_name&#39;
config_s3_key = &#39;config.json&#39;

def read_s3_config():
    s3_client = boto3.client(
        &#39;s3&#39;,
        config_s3_region,
    )

    s3_client.download_file(
        config_s3_bucket,
        config_s3_key,
        &#39;/tmp/%s&#39; % config_s3_key
    )

    full_config = (open(&#39;/tmp/%s&#39; % config_s3_key).read())
    return json.loads(full_config)

def route53_client(
  execution_mode,
  aws_region,
  route_53_zone_id,
  route_53_record_name,
  route_53_record_ttl,
  route_53_record_type,
  public_ip
  ):

    route53_client = boto3.client(
        &#39;route53&#39;,
        region_name=aws_region
    )

    if execution_mode == &#39;get_record&#39;:
        current_route53_record_set = route53_client.list_resource_record_sets(
            HostedZoneId=route_53_zone_id,
            StartRecordName=route_53_record_name,
            StartRecordType=route_53_record_type,
            MaxItems=&#39;2&#39;
        )

        for eachRecord in current_route53_record_set[&#39;ResourceRecordSets&#39;]:
            if eachRecord[&#39;Name&#39;] == route_53_record_name:
                if len(eachRecord[&#39;ResourceRecords&#39;]) == 1:
                    for eachSubRecord in eachRecord[&#39;ResourceRecords&#39;]:
                        currentroute53_ip = eachSubRecord[&#39;Value&#39;]
                        return_status = &#39;success&#39;
                        return_message = currentroute53_ip
                        return {&#39;return_status&#39;: return_status,
                                &#39;return_message&#39;: return_message}
                elif len(eachRecord[&#39;ResourceRecords&#39;]) &amp;gt; 1:
                    return_status = &#39;fail&#39;
                    return_message = &#39;You should only have a single value for&#39;\
                    &#39; your dynamic record.  You currently have more than one.&#39;
                    return {&#39;return_status&#39;: return_status,
                            &#39;return_message&#39;: return_message}

    if execution_mode == &#39;set_record&#39;:
        change_route53_record_set = route53_client.change_resource_record_sets(
            HostedZoneId=route_53_zone_id,
            ChangeBatch={
                &#39;Changes&#39;: [
                    {
                        &#39;Action&#39;: &#39;UPSERT&#39;,
                        &#39;ResourceRecordSet&#39;: {
                            &#39;Name&#39;: route_53_record_name,
                            &#39;Type&#39;: route_53_record_type,
                            &#39;TTL&#39;: route_53_record_ttl,
                            &#39;ResourceRecords&#39;: [
                                {
                                    &#39;Value&#39;: public_ip
                                }
                            ]
                        }
                    }
                ]
            }
        )
        return 1

def run_set_mode(set_hostname, validation_hash, source_ip):
    try:
        full_config = read_s3_config()
    except:
        return_status = &#39;fail&#39;
        return_message = &#39;There was an issue finding &#39;\
            &#39;or reading the S3 config file.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}

    record_config_set = full_config[set_hostname]
    aws_region = record_config_set[&#39;aws_region&#39;]
    route_53_zone_id = record_config_set[&#39;route_53_zone_id&#39;]
    route_53_record_ttl = record_config_set[&#39;route_53_record_ttl&#39;]
    route_53_record_type = record_config_set[&#39;route_53_record_type&#39;]
    shared_secret = record_config_set[&#39;shared_secret&#39;]

    if not re.match(r&#39;[0-9a-fA-F]{64}&#39;, validation_hash):
        return_status = &#39;fail&#39;
        return_message = &#39;You must pass a valid sha256 hash in the &#39;\
            &#39;hash= argument.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}


    calculated_hash = hashlib.sha256(source_ip + set_hostname + shared_secret).hexdigest()

    if not calculated_hash == validation_hash:
        return_status = &#39;fail&#39;
        return_message = &#39;Validation hashes do not match.&#39;
        return {&#39;return_status&#39;: return_status,
                &#39;return_message&#39;: return_message}
    else:
        route53_get_response = route53_client(
            &#39;get_record&#39;,
            aws_region,
            route_53_zone_id,
            set_hostname,
            route_53_record_ttl,
            route_53_record_type,
            &#39;&#39;)

        if not route53_get_response:
            route53_ip = &#39;0&#39;
        elif route53_get_response[&#39;return_status&#39;] == &#39;fail&#39;:
            return_status = route53_get_response[&#39;return_status&#39;]
            return_message = route53_get_response[&#39;return_message&#39;]
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}
        else:
            route53_ip = route53_get_response[&#39;return_message&#39;]

        if route53_ip == source_ip:
            return_status = &#39;success&#39;
            return_message = &#39;Your IP address matches &#39;\
                &#39;the current Route53 DNS record.&#39;
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}
        else:
            return_status = route53_client(
                &#39;set_record&#39;,
                aws_region,
                route_53_zone_id,
                set_hostname,
                route_53_record_ttl,
                route_53_record_type,
                source_ip)
            return_status = &#39;success&#39;
            return_message = &#39;Your hostname record &#39; + set_hostname +\
                &#39; has been set to &#39; + source_ip
            return {&#39;return_status&#39;: return_status,
                    &#39;return_message&#39;: return_message}

def lambda_handler(event, context):

    execution_mode = event[&#39;execution_mode&#39;]
    source_ip = event[&#39;source_ip&#39;]
    query_string = event[&#39;query_string&#39;]
    validation_hash = event[&#39;validation_hash&#39;]
    set_hostname = event[&#39;set_hostname&#39;]

    execution_modes = (&#39;set&#39;, &#39;get&#39;)
    if execution_mode not in execution_modes:
        return_status = &#39;fail&#39;
        return_message = &#39;You must pass mode=get or mode=set arguments.&#39;
        return_dict = {&#39;return_status&#39;: return_status,
                       &#39;return_message&#39;: return_message}

    if execution_mode == &#39;get&#39;:
        return_status = &#39;success&#39;
        return_message = source_ip
        return_dict = {&#39;return_status&#39;: return_status,
                       &#39;return_message&#39;: return_message}
    else:
        return_dict = run_set_mode(set_hostname, validation_hash, source_ip)

    return return_dict

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of the configuration file stored in S3 is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-javascript&#34;&gt;
{
    &amp;quot;host1.dyn.example.com.&amp;quot;: {
        &amp;quot;aws_region&amp;quot;: &amp;quot;us-west-2&amp;quot;,
        &amp;quot;route_53_zone_id&amp;quot;: &amp;quot;MY_ZONE_ID&amp;quot;,
        &amp;quot;route_53_record_ttl&amp;quot;: 60,
        &amp;quot;route_53_record_type&amp;quot;: &amp;quot;A&amp;quot;,
        &amp;quot;shared_secret&amp;quot;: &amp;quot;SHARED_SECRET_1&amp;quot;
    },
    &amp;quot;host2.dyn.example.com.&amp;quot;: {
        &amp;quot;aws_region&amp;quot;: &amp;quot;us-west-2&amp;quot;,
        &amp;quot;route_53_zone_id&amp;quot;: &amp;quot;MY_ZONE_ID&amp;quot;,
        &amp;quot;route_53_record_ttl&amp;quot;: 60,
        &amp;quot;route_53_record_type&amp;quot;: &amp;quot;A&amp;quot;,
        &amp;quot;shared_secret&amp;quot;: &amp;quot;SHARED_SECRET_2&amp;quot;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An example of a &lt;code&gt;bash&lt;/code&gt;-based client which can be set up as a &lt;code&gt;cron&lt;/code&gt; job is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;
#!/bin/bash

#./dynamic_dns_lambda_client.sh host1.dyn.example.com. SHARED_SECRET_1 &amp;quot;abc123.execute-api.us-west-2.amazonaws.com/prod&amp;quot;

if [ $# -eq 0 ]
    then
    echo &amp;quot;Usage: $0 host1.dyn.example.com. sharedsecret \&amp;quot;abc123.execute-api.us-west-2.amazonaws.com/prod\&amp;quot;&amp;quot;
    exit
fi

myHostname=$1
mySharedSecret=$2
myAPIURL=$3

myIP=`curl -q -s  &amp;quot;https://$myAPIURL?mode=get&amp;quot; | egrep -o &#39;[0-9\.]+&#39;`
myHash=`echo -n $myIP$myHostname$mySharedSecret | shasum -a 256 | awk &#39;{print $1}&#39;`

curl -q -s &amp;quot;https://$myAPIURL?mode=set&amp;amp;hostname=$myHostname&amp;amp;hash=$myHash&amp;quot;
echo

&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>AWS Security Groups and Dynamic IP Addresses</title>
      <link>https://joshua_hull.gitlab.io/post/2016-06-10-aws-security-groups-dynamic-ips/</link>
      <pubDate>Fri, 10 Jun 2016 09:48:12 -0400</pubDate>
      <guid>https://joshua_hull.gitlab.io/post/2016-06-10-aws-security-groups-dynamic-ips/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You are given the task to only allow access to certain AWS resources to the office you work in. You create a Security Group and ask a colleague for the external IP address range assigned to the office. He tells you that there is not static range. The office, along with the rest of the building, share a commercial ISP with dynamic addresses. In addition to that, there is not one but three IPSs that are load balanced for outgoing traffic. The external IP address of the machine you&#39;re working on can theoretically change on a per request basis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You decide there&#39;s nothing you can do about the rest of the building being able to access the resources. It isn&#39;t a security threat if they can and there no obvious way that they would be able to find the resources. You decide you&#39;ll write a Python script that gets your public IP address. It will then calculate if that address is in the list of address ranges you already know about. If the address is in the range then everything is good. If the address isn&#39;t in the range then the script will calculate a new range so you can go update the Security Group.&lt;/p&gt;
&lt;p&gt;After an hour or so you have the following script working:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;
import urllib.request as urllib2
import re
from netaddr import *
import pprint

# Get our external network address and calculate a Class C CIDR based on it.
resource = urllib2.urlopen(&#39;http://ipinfo.io/ip&#39;)
ext_ip =  resource.read().decode(resource.headers.get_content_charset())
my_ip = re.match(r&amp;quot;^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$&amp;quot;,ext_ip).group()
my_cidr = IPNetwork(my_ip)
my_cidr.prefixlen = 24
my_cidr = my_cidr.cidr

print(&amp;quot;IP Address: &amp;quot; + str(my_ip))
print(&amp;quot;CIDR: &amp;quot; + str(my_cidr))


# The list of existing CIDR ranges
lines = []
ip_list = []
with open(&#39;Desktop/AWS/IPs.txt&#39;) as infile:
    for line in infile:
        ip_list.append(IPNetwork(line))
        lines.append(line)

# Find the largest ranges that cover our CIDR ranges.
ip_merged = cidr_merge(ip_list)

# Try to find if we are in an existing CIDR by looking at the first address in
# the range.
in_range = False
for network in ip_merged:
    if my_cidr[0] == network[0]:
        in_range = True
        print(&amp;quot;Possible match: &amp;quot; + str(network))
        pass
    pass

if in_range:
    print(&amp;quot;We should be in the existing range:&amp;quot;)
    pass
else:
    print(&amp;quot;We might not be in the existing range. Proposed new range:&amp;quot;)
    ip_list.append(my_cidr)
    ip_merged = cidr_merge(ip_list)
    pass

lines = []
for network in ip_merged:
    lines.append(str(network))

with open(&#39;Desktop/AWS/IPs.txt&#39;, &#39;w&#39;) as outfile:
    for line in lines:
        outfile.write(line)

pprint.pprint(ip_merged)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whenever someone in your office complains that they can&#39;t access the resources they need you simple have them run this script. If it states that the existing range should match then you have your colleague run the script several times spaced several minutes apart. This should allow the script to catch the new IP address the ISP is using.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extra Credit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You decide that having to have your colleagues run this script whenever their work is interrupted is not sufficient. You want to have the script run on a schedule and update the Security Group on it&#39;s own. You append the following to the python script to facilitate the automatic updates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;ip_replace_contents = &#39;&#39;

for network in ip_merged:
  ip_replace_contents = ip_replace_contents + &amp;quot;\&amp;quot;&amp;quot; + str(network) &amp;quot;\&amp;quot;, &amp;quot;
  pass
ip_replace_contents = ip_replace_contents.rstrip(&amp;quot;, &amp;quot;)

replacements = {&#39;IP_REPLACE_CONTENTS&#39;:ip_replace_contents}

with open(&#39;Desktop/Terraform/Security_Groups.tf&#39;) as infile, open(&#39;Desktop/Terraform/&#39; + datetime.datetime.now().strftime(&amp;quot;%Y-%m-%d&amp;quot;) + &#39;/Security_Groups.tf&#39;, &#39;w&#39;) as outfile:
    for line in infile:
        for src, target in replacements.iteritems():
            line = line.replace(src, target)
        outfile.write(line)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You schedule the following shell script to run every week day at 6:00 AM to update the Security Group before the work day begins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash

NOW=$(date +&amp;quot;%Y-%m-%d&amp;quot;)

python dynamic_ips.py
terraform apply Desktop/Terraform/$NOW

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everyone in the office is happy knowing that the resources on AWS are safe and they don&#39;t have to worry about not being able to access them themselves.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
